/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "spatial_master_epoch.h"

#include "hilog_wrapper.h"

namespace OHOS {
namespace Msdp {
void SpatialMasterEpoch::Start()
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s SpatialMasterEpoch enter", __func__);
    alive_ = true;

    if (listener_ == nullptr) {
        listener_ = std::make_shared<MasterListener>(shared_from_this());
    }

    HandleDeviceState();
    coordinateSystem_->RegisterUpdateListener(listener_);
    communicator_->RegisterSessionListener(listener_);
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s Master get trustDeviceList", __func__);
    communicator_->GetTrustDeviceList([this](GetDeviceEvent event, const std::vector<DeviceInfo>& trustDeviceList) {
        switch (event) {
            case GetDeviceEvent::GET_SUCCESS:
                this->serverCandidates_.clear();
                this->foundDevices_.clear();
                if (trustDeviceList.empty()) {
                    SPATIAL_LOGE(MODULE_SERVICE, "trustDeviceList is empty");
                    return;
                }
                getTrustFlag_ = false;
                SPATIAL_LOGI(MODULE_SERVICE, "%{public}s trustDeviceList.size: %{public}zu",
                    __func__, trustDeviceList.size());
                for (auto &device : trustDeviceList) {
                    if (device.IsRichDevice()) {
                        SPATIAL_LOGI(MODULE_SERVICE, "device is rich device, start connect");
                        communicating_ = true;
                        this->Connect(device);
                    }
                    this->foundDevices_.push_back(device);
                    serverCandidates_.push_back(device);
                }
                break;
            case GetDeviceEvent::GET_FAILED:
                if (trustDeviceList.empty()) {
                    SPATIAL_LOGI(MODULE_SERVICE, "get failed trustDeviceList is empty");
                    coordinateSystem_->UnRegsterUpdateLister(listener_);
                    SendEvent(EVENT_EPOCH_MASTER_AGAIN, 0, GetCommunicateTime());
                    getTrustFlag_ = true;
                    return;
                }
                break;
            default:
                break;
        }
    });
    if (getTrustFlag_) {
        SPATIAL_LOGE(MODULE_SERVICE, "failed to get trustDeviceList");
        return;
    }
    SendEvent(EVENT_EPOCH_MASTER_START);
    SendEvent(EVENT_MASTER_EPOCH_COMMUNICATE_END, 0, GetCommunicateTime());
}

void SpatialMasterEpoch::End()
{
    SPATIAL_LOGI(MODULE_SERVICE, "master epoch end");
    alive_ = false;
    coordinateSystem_->UnRegsterUpdateLister(listener_);
    communicator_->UnregisterDeviceStateListener();
    RemoveEvent(EVENT_MASTER_EPOCH_COMMUNICATE_END);
    SendEvent(EVENT_EPOCH_MASTER_END);
}

uint32_t SpatialMasterEpoch::GetEpoch()
{
    return EPOCH_MASTER;
}

bool SpatialMasterEpoch::HandleEvent(int32_t event, int64_t param)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "SpatialMasterEpoch Event: %{public}d", event);
    switch (event) {
        case EVENT_MASTER_EPOCH_COMMUNICATE_END:
            communicating_ = false;
            if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
                SPATIAL_LOGI(MODULE_SERVICE,
                    "Update device failed");
                End();
            } else {
                SendEvent(EVENT_EPOCH_MASTER_END);
            }
            return true;
        default:
            break;
    }
    return false;
}

void SpatialMasterEpoch::Connect(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Connect enter");
    if (!communicating_) {
        return;
    }
    communicator_->Connect(device, [this](ConnectEvent event, const DeviceInfo& device) {
        SPATIAL_LOGI(MODULE_SERVICE,
            "Connect: %{public}d", event);
        switch (event) {
            case CONNECTED:
                HandleConnected(device);
                break;
            case DISCONNECTED: // fallthrough
            case CONNECT_FAILED:
                SPATIAL_LOGI(MODULE_SERVICE, "handle disconnect");
                HandleDisconnected(device);
                break;
            default:
                break;
        }
    });
}

void SpatialMasterEpoch::HandleConnected(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "HandleConnected: enter");
    connectedDevice_ = device;
    MsdpDeviceType type = static_cast<MsdpDeviceType>(device.typeId);
    switch (type) {
        case MsdpDeviceType::TYPE_AUDIO:
        case MsdpDeviceType::TYPE_WIFI_CAMERA:
        case MsdpDeviceType::TYPE_PC:
        case MsdpDeviceType::TYPE_PAD:
        case MsdpDeviceType::TYPE_WATCH: {
            break;
        }
        case MsdpDeviceType::TYPE_PHONE:
        case MsdpDeviceType::TYPE_CAR:
        case MsdpDeviceType::TYPE_TV:
        case MsdpDeviceType::TYPE_UNKNOWN: {
            SPATIAL_LOGI(MODULE_SERVICE, "%{public}s MasterEpoch opensession, peerDeviceId: %{public}s",
                __func__, device.deviceId.c_str());
            communicator_->OpenSession(device);
            break;
        }
        default:
            break;
    }
}

void SpatialMasterEpoch::HandleDisconnected(const DeviceInfo& device)
{
    if (!communicating_) {
        SPATIAL_LOGI(MODULE_SERVICE,
            "Call UpdateDevice after communication is done");
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "seccond Update device failed");
            End();
        }
    }
    SPATIAL_LOGI(MODULE_SERVICE, "serverCandidates_.size=%{public}d", serverCandidates_.size());
    if (!serverCandidates_.empty()) {
        DeviceInfo device = serverCandidates_.front();
        serverCandidates_.pop_front();
        Connect(device);
    } else if (communicator_->IsGetDeviceComplete()) {
        RemoveEvent(EVENT_MASTER_EPOCH_COMMUNICATE_END);
        SendEvent(EVENT_MASTER_EPOCH_COMMUNICATE_END);
    }
}

void SpatialMasterEpoch::HandleGetMapResponse(std::shared_ptr<SpatialResponse> response)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "HandleGetMap status: %{public}d", response->status);
    if (response->status != SUCCESS) {
        SPATIAL_LOGI(MODULE_SERVICE, "don't have map");
        communicator_->Disconnect(connectedDevice_);
        return;
    }
    std::shared_ptr<SpatialMap> map = response->map;
    mergingTop = map->top;
    if (!coordinateSystem_->MergeMap(map)) {
        SPATIAL_LOGI(MODULE_SERVICE, "don't need to merge");
        communicator_->Disconnect(connectedDevice_);
        return;
    }
}

void SpatialMasterEpoch::HandleSyncMapOfferResponse(std::shared_ptr<SpatialResponse> response)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "HandleSyncMapOffer status: %{public}d", response->status);
    if (response->status == ACCEPT) {
        std::shared_ptr<SpatialMap> map = coordinateSystem_->Commit();
        auto session = communicator_->GetCommunicatorSession(response->sessionId);
        if (session != nullptr) {
            session.get()->RequestSyncMap(map);
        }
    }
    return;
}

void SpatialMasterEpoch::HandleSyncMapResponse(std::shared_ptr<SpatialResponse> response)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "HandleSyncMap status: %{public}d", response->status);
}

void SpatialMasterEpoch::MasterListener::OnSessionOpen(
    std::shared_ptr<SpatialCommunicator::Session> session)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Master OnSessionOpen: %{public}d", session->GetSessionId());

    std::shared_ptr<SpatialMasterEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "master epoch is null");
        return;
    }

    if (masterExist_) {
        SPATIAL_LOGI(MODULE_SERVICE, "master as session already exists");
        return;
    }
    epoch->sessionId_ = session->GetSessionId();
    int role = session->GetRole();
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s device role: %{public}d", __func__, role);
    if (role != SpatialCommunicator::ROLE_CLIENT) {
        SPATIAL_LOGI(MODULE_SERVICE, "%{public}s master to slave and as server", __func__);
        epoch->SendEvent(EVENT_EPOCH_MASTER_DEMOTE);
    } else {
        masterExist_ = true;
        session.get()->RequestGetMap();
    }
}

void SpatialMasterEpoch::MasterListener::OnSessionClose(
    std::shared_ptr<SpatialCommunicator::Session> session)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Master OnSessionClose: %{public}d", session->GetSessionId());
    std::shared_ptr<SpatialMasterEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "master epoch is null");
        return;
    }
    if (masterExist_) {
        session = nullptr;
        masterExist_ = false;
    }
    if (session == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "master session is null");
        return;
    }
    epoch->communicator_->Disconnect(session->GetDeviceInfo());
}

void SpatialMasterEpoch::MasterListener::OnReceiveRequest(
    std::shared_ptr<SpatialCommunicator::Session> session,
    std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Master OnReceiveRequest: %{public}d", session->GetSessionId());

    std::shared_ptr<SpatialMasterEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        return;
    }
    switch (request->requestId) {
        case SpatialRequestId::REQUEST_GET_MAP:
            epoch->HandleGetMapRequest(request);
            break;
        case SpatialRequestId::REQUEST_SYNC_MAP_OFFER:
            epoch->HandleSyncMapOfferRequest(request);
            break;
        case SpatialRequestId::REQUEST_SYNC_MAP:
            epoch->HandleSyncMapRequest(request);
            break;
        default:
            break;
    }
}

void SpatialMasterEpoch::HandleGetMapRequest(std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master HandleGetMapRequest.request->sessionId: %{public}d", request->sessionId);
    auto session = communicator_->GetCommunicatorSession(request->sessionId);
    if (session == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "Master session is nullptr");
        return;
    }
    session->ResponseGetMap(coordinateSystem_->Commit());
}

void SpatialMasterEpoch::HandleSyncMapOfferRequest(std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master HandleSyncMapOfferRequest");
    communicator_->GetCommunicatorSession(request->sessionId).get()->ResponseSyncMapOffer(false);
}

void SpatialMasterEpoch::HandleSyncMapRequest(std::shared_ptr<SpatialRequest> request)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master HandleSyncMapRequest");
    communicator_->GetCommunicatorSession(request->sessionId).get()->ResponseSyncMap(false);
}

void SpatialMasterEpoch::MasterListener::OnReceiveResponse(
    std::shared_ptr<SpatialCommunicator::Session> session,
    std::shared_ptr<SpatialResponse> response)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Master OnReceiveResponse: %{public}d, response->response: %{public}d",
        session->GetSessionId(), response->responseId);
    std::shared_ptr<SpatialMasterEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        return;
    }

    switch (response->responseId) {
        case SpatialResponseId::RESPONSE_GET_MAP:
            epoch->HandleGetMapResponse(response);
            break;
        case SpatialResponseId::RESPONSE_SYNC_MAP_OFFER:
            epoch->HandleSyncMapOfferResponse(response);
            break;
        case SpatialResponseId::RESPONSE_SYNC_MAP:
            epoch->HandleSyncMapResponse(response);
            break;
        default:
            break;
    }
    return;
}

void SpatialMasterEpoch::HandleDeviceState()
{
    communicator_->RegisterDeviceStateListener([this](DeviceStateEvent event, const DeviceInfo& device) {
        switch (event) {
            case DeviceStateEvent::ONLINE:
                OnDeviceOnline(device);
                break;
            case DeviceStateEvent::OFFLINE:
                OnDeviceOffline(device);
                break;
            case DeviceStateEvent::CHANGED:
                OnDeviceChanged(device);
                break;
            case DeviceStateEvent::READY:
                OnDeviceReady(device);
                break;
            default:
                break;
        }
    });
}

void SpatialMasterEpoch::OnDeviceOnline(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master OnDeviceOnline: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }
    for (auto &deviceIter : foundDevices_) {
        if (deviceIter.deviceId == device.deviceId) {
            SPATIAL_LOGI(MODULE_SERVICE, "device is exist");
            exist_ = true;
        }
    }
    if (!exist_) {
        foundDevices_.push_back(device);
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}

void SpatialMasterEpoch::OnDeviceReady(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master OnDeviceReady: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }
    for (auto &deviceIter : foundDevices_) {
        if (deviceIter.deviceId == device.deviceId) {
            deviceIter = device;
            exist_ = true;
        }
    }
    if (exist_) {
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}

void SpatialMasterEpoch::OnDeviceOffline(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master OnDeviceOffline: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }
    for (auto iter = foundDevices_.begin(); iter != foundDevices_.end();) {
        if (iter->deviceId == device.deviceId) {
            iter = foundDevices_.erase(iter);
            exist_ = true;
        } else {
            iter++;
        }
    }
    if (exist_) {
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}

void SpatialMasterEpoch::OnDeviceChanged(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master OnDeviceChanged: deviceList.size: %{public}zu", foundDevices_.size());
    if (foundDevices_.empty()) {
        SPATIAL_LOGE(MODULE_SERVICE, "foundDevices_ is empty");
        return;
    }
    if (coordinateSystem_ == nullptr) {
        SPATIAL_LOGE(MODULE_SERVICE, "coordinateSystem_ is nullptr");
        return;
    }
    for (auto &deviceIter : foundDevices_) {
        if (deviceIter.deviceId == device.deviceId) {
            deviceIter = device;
            exist_ = true;
        }
    }
    if (exist_) {
        if (!coordinateSystem_->UpdateDevices(foundDevices_)) {
            SPATIAL_LOGI(MODULE_SERVICE,
                "Update device failed");
        }
    }
}

void SpatialMasterEpoch::MasterListener::OnDeviceAdded(const DeviceInfo& device,
    const DeviceCoordinate& coordinate)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Master Epoch g: %{public}s", device.deviceId.c_str());
}

void SpatialMasterEpoch::MasterListener::OnDeviceRemoved(const DeviceInfo& device)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Master Epoch OnDeviceRemoved: %{public}s", device.deviceId.c_str());
}

void SpatialMasterEpoch::MasterListener::OnDeviceCoordinateUpdate(
    const DeviceInfo& device, const DeviceCoordinate& coordinate)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "Master Epoch OnDeviceCoordinateUpdate: %{public}s", device.deviceId.c_str());
}

void SpatialMasterEpoch::MasterListener::OnUpdateEnd()
{
    SPATIAL_LOGI(MODULE_SERVICE, "Master Epoch OnUpdateEnd");
    std::shared_ptr<SpatialMasterEpoch> epoch = owner_.lock();
    if (epoch == nullptr) {
        return;
    }
    if (epoch->communicating_) {
        if (epoch->mergingTop != 0) {
            auto session = epoch->communicator_->GetCommunicatorSession(epoch->sessionId_);
            if (session != nullptr) {
                session.get()->RequestSyncMapOffer(epoch->mergingTop);
            }
            epoch->mergingTop = 0;
        }
    } else {
        epoch->End();
    }
}
} // namespace Msdp
} // namespace OHOS